<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Gist Audio Player</title>
    <style>
        body {
            font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            max-width: 800px;
            margin: 20px auto;
            padding: 0 20px;
            line-height: 1.6;
        }
        .info {
            background: #e8f4ff;
            padding: 15px;
            border-radius: 4px;
            margin: 20px 0;
            border-left: 4px solid #0066cc;
        }
        .input-group {
            display: flex;
            gap: 8px;
            margin-bottom: 20px;
        }
        input {
            flex: 1;
            padding: 8px 12px;
            font-size: 16px;
            border: 1px solid #ccc;
            border-radius: 4px;
        }
        button {
            padding: 8px 16px;
            font-size: 16px;
            background: #0066cc;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:disabled {
            background: #cccccc;
        }
        button:hover:not(:disabled) {
            background: #0055aa;
        }
        .error {
            color: #cc0000;
            margin: 10px 0;
        }
        .player-container {
            margin: 20px 0;
        }
        audio {
            width: 100%;
            margin: 10px 0;
        }
        .transcript {
            background: #f5f5f5;
            padding: 15px;
            border-radius: 4px;
            margin: 10px 0;
        }
        .loading {
            color: #666;
            font-style: italic;
        }
        .progress {
            margin: 10px 0;
            padding: 10px;
            background: #f0f0f0;
            border-radius: 4px;
        }
    </style>
</head>
<body>
    <div class="info">
        Note: This player expects GitHub Gists containing JSON responses from the OpenAI GPT-4 with audio preview model 
        (<code>gpt-4o-audio-preview</code>). The JSON should include an audio response with base64-encoded WAV data.
    </div>

    <div class="input-group">
        <input type="url" id="gistUrl" placeholder="Enter Gist URL" aria-label="Gist URL">
        <button id="fetchBtn">Fetch</button>
    </div>
    <div id="error" class="error" style="display: none;"></div>
    <div id="progress" class="progress" style="display: none;"></div>
    <div id="playerContainer" class="player-container" style="display: none;">
        <audio id="audioPlayer" controls></audio>
        <button id="downloadBtn">Download Audio</button>
        <div id="transcript" class="transcript"></div>
    </div>

    <script>
        const gistInput = document.getElementById('gistUrl');
        const fetchBtn = document.getElementById('fetchBtn');
        const errorDiv = document.getElementById('error');
        const progressDiv = document.getElementById('progress');
        const playerContainer = document.getElementById('playerContainer');
        const audioPlayer = document.getElementById('audioPlayer');
        const downloadBtn = document.getElementById('downloadBtn');
        const transcriptDiv = document.getElementById('transcript');

        function showError(message) {
            errorDiv.textContent = message;
            errorDiv.style.display = 'block';
            playerContainer.style.display = 'none';
        }

        function showProgress(message) {
            progressDiv.textContent = message;
            progressDiv.style.display = 'block';
        }

        function hideProgress() {
            progressDiv.style.display = 'none';
        }

        function clearError() {
            errorDiv.style.display = 'none';
        }

        function extractGistId(url) {
            if (!url) return null;
            // Handle both full URLs and just IDs
            const parts = url.split('/');
            return parts[parts.length - 1];
        }

        function updateURL(gistId) {
            const newUrl = new URL(window.location);
            newUrl.searchParams.set('gist', gistId);
            history.pushState({}, '', newUrl);
        }

        async function fetchWithRetry(url, options = {}, retries = 3) {
            for (let i = 0; i < retries; i++) {
                try {
                    const response = await fetch(url, options);
                    if (!response.ok) {
                        throw new Error(`HTTP error! status: ${response.status}`);
                    }
                    return response;
                } catch (error) {
                    if (i === retries - 1) throw error;
                    await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
                }
            }
        }

        async function fetchGistContent(gistId) {
            showProgress('Fetching Gist metadata...');
            const gistResponse = await fetchWithRetry(`https://api.github.com/gists/${gistId}`);
            const gistData = await gistResponse.json();
            
            const files = gistData.files;
            if (!files || Object.keys(files).length === 0) {
                throw new Error('No files found in Gist');
            }

            const firstFile = Object.values(files)[0];
            
            // Check if content is truncated
            if (firstFile.truncated) {
                showProgress('Content is truncated, fetching raw content...');
                const rawResponse = await fetchWithRetry(firstFile.raw_url);
                return await rawResponse.text();
            }
            
            return firstFile.content;
        }

        async function processGistUrl(url) {
            try {
                fetchBtn.disabled = true;
                clearError();
                hideProgress();
                
                const gistId = extractGistId(url);
                if (!gistId) throw new Error('Invalid Gist URL');

                updateURL(gistId);
                if (gistInput.value !== url) {
                    gistInput.value = url;
                }

                const content = await fetchGistContent(gistId);
                
                showProgress('Parsing JSON content...');
                let jsonContent;
                try {
                    jsonContent = JSON.parse(content);
                } catch (e) {
                    throw new Error('Failed to parse JSON content. The content might be corrupted or incomplete.');
                }

                if (!jsonContent?.choices?.[0]?.message?.audio?.data) {
                    throw new Error('This Gist does not contain a valid GPT-4 audio response');
                }

                showProgress('Processing audio data...');
                const audioData = jsonContent.choices[0].message.audio.data;
                const transcript = jsonContent.choices[0].message.audio.transcript;

                const binaryData = atob(audioData);
                const arrayBuffer = new ArrayBuffer(binaryData.length);
                const uint8Array = new Uint8Array(arrayBuffer);
                for (let i = 0; i < binaryData.length; i++) {
                    uint8Array[i] = binaryData.charCodeAt(i);
                }
                const blob = new Blob([uint8Array], { type: 'audio/wav' });
                const audioUrl = URL.createObjectURL(blob);

                audioPlayer.src = audioUrl;
                transcriptDiv.textContent = transcript;
                playerContainer.style.display = 'block';
                hideProgress();

                downloadBtn.onclick = () => {
                    const a = document.createElement('a');
                    a.href = audioUrl;
                    a.download = 'audio.wav';
                    document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a);
                };
            } catch (error) {
                showError(error.message || 'An error occurred');
            } finally {
                fetchBtn.disabled = false;
                hideProgress();
            }
        }

        fetchBtn.addEventListener('click', () => {
            const url = gistInput.value.trim();
            if (!url) {
                showError('Please enter a Gist URL');
                return;
            }
            processGistUrl(url);
        });

        gistInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                fetchBtn.click();
            }
        });

        window.addEventListener('load', () => {
            const params = new URLSearchParams(window.location.search);
            const gistId = params.get('gist');
            if (gistId) {
                const fullUrl = `https://gist.github.com/${gistId}`;
                gistInput.value = fullUrl;
                processGistUrl(fullUrl);
            }
        });

        window.addEventListener('popstate', () => {
            const params = new URLSearchParams(window.location.search);
            const gistId = params.get('gist');
            if (gistId) {
                const fullUrl = `https://gist.github.com/${gistId}`;
                processGistUrl(fullUrl);
            } else {
                gistInput.value = '';
                playerContainer.style.display = 'none';
                clearError();
            }
        });
    </script>
</body>
</html>
